package com.tang;


import com.baomidou.mybatisplus.core.MybatisConfiguration;
import lombok.Getter;
import lombok.Setter;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.io.Resource;
import org.springframework.core.type.ClassMetadata;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Collection;
import java.util.Objects;

@Getter
@Setter
public class MyBatisPlusConfiguration implements BeanDefinitionRegistryPostProcessor, ApplicationListener<ContextRefreshedEvent> {
    private MybatisConfiguration configuration;
    String mapperLocation;
    String mapperScan;
    String serviceScan;

    SqlSessionFactory sqlSessionFactory(DataSource dataSource) {
        configuration = new MybatisConfiguration();
        Environment environment = new Environment("mybatis", new JdbcTransactionFactory(), dataSource);
        configuration.setEnvironment(environment);
        return new DefaultSqlSessionFactory(configuration);
    }

    SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) {
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class).getBeanDefinition();
        beanDefinitionRegistry.registerBeanDefinition("configurationClassPostProcessor", beanDefinition);
        scanMappers(beanDefinitionRegistry);
    }

    private void scanMappers(BeanDefinitionRegistry beanDefinitionRegistry) {
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(beanDefinitionRegistry);
        scanner.setMarkerInterface(com.baomidou.mybatisplus.core.mapper.Mapper.class);
        scanner.setAddToConfig(true);
        scanner.registerFilters();
        scanner.scan(mapperScan);

        ClassPathBeanDefinitionScanner classPathBeanDefinitionScanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry);
        classPathBeanDefinitionScanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> {
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
            String[] interfaceNames = classMetadata.getInterfaceNames();
            if (Objects.nonNull(interfaceNames)) {
                for (String interfaceName : interfaceNames) {
                    if (interfaceName.equals("com.baomidou.mybatisplus.extension.service.IService")) {
                        return true;
                    }
                }
            }
            return false;
        });
        classPathBeanDefinitionScanner.scan(serviceScan);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        DataSource dataSource = configurableListableBeanFactory.getBean(DataSource.class);
        SqlSessionFactory sqlSessionFactory = sqlSessionFactory(dataSource);
        SqlSessionTemplate sqlSessionTemplate = sqlSessionTemplate(sqlSessionFactory);

        configurableListableBeanFactory.registerSingleton("sqlSessionTemplate", sqlSessionTemplate);
        configurableListableBeanFactory.registerSingleton("mybatisSqlSessionFactory", sqlSessionFactory);
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        if (Objects.isNull(configuration)) {
            DataSource dataSource = applicationContext.getBean(DataSource.class);
            SqlSessionFactory sqlSessionFactory = sqlSessionFactory(dataSource);
            SqlSessionTemplate sqlSessionTemplate = sqlSessionTemplate(sqlSessionFactory);

            try {
                Resource[] resources = applicationContext.getResources(mapperLocation);
                for (Resource mapperLocation : resources) {
                    XMLMapperBuilder xmlConfigBuilder =
                            new XMLMapperBuilder(mapperLocation.getInputStream(),
                                    configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    xmlConfigBuilder.parse();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        try {
            Resource[] resources = applicationContext.getResources(mapperLocation);
            Collection<Class<?>> mappers = configuration.getMapperRegistry().getMappers();
            for (Class<?> mapper : mappers) {
                applicationContext.getBean(mapper);
            }

            for (Resource mapperLocation : resources) {
                XMLMapperBuilder xmlConfigBuilder =
                        new XMLMapperBuilder(mapperLocation.getInputStream(),
                                configuration, mapperLocation.toString(), configuration.getSqlFragments());
                xmlConfigBuilder.parse();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
}
